////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Microsoft Corporation.  All rights reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Gadgeteer.Interfaces
{
    using System;
    using Microsoft.SPOT;
    using Microsoft.SPOT.Hardware;
    using Gadgeteer.Modules;
    // I2C Bus provides support for multiple I2C devices sharing the bus.
    /// <summary>
    /// Represents a device on an Inter-Integrated Circuit (I2C) multi-drop 2-wire bus.
    /// </summary>
    public class I2CBus
    {
        /// <summary>
        /// Gets or sets the address of the <see cref="I2CBus"/> device.
        /// </summary>
        public int Address {
            get
            {
                return this.configuration.Address;
            }
            set
            {
                this.configuration = new I2CDevice.Configuration((ushort) value, this.configuration.ClockRateKhz);
            }       
        }

        private static I2CDevice device;
        private I2CDevice.Configuration configuration;

        private static object i2CLock = new object();

        /// <summary>
        /// Global lock used to control access to the I2C bus.
        /// </summary>
        internal static object I2CLock
        {
            get { return i2CLock; }
        }

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <remarks>This automatically checks that the socket supports Type I, and reserves the SDA and SCL pins.
        /// An exception will be thrown if there is a problem with these checks.</remarks>
        /// <param name="address">The address for the I2C device.</param>
        /// <param name="clockRateKhz">The clock rate, in kHz, used when communicating with the I2C device.</param>
        /// <param name="socket">The socket for this I2C device interface.</param>
        /// <param name="module">The module using this I2C interface, which can be null if unspecified.</param>
        public I2CBus(Socket socket, ushort address, int clockRateKhz, Module module)
        {
            socket.EnsureTypeIsSupported('I', module);
            
            lock (I2CLock)
            {
                if (device == null)
                {
                    socket.ReservePin(Socket.Pin.Eight, module);
                    socket.ReservePin(Socket.Pin.Nine, module);
                    
                    device = new I2CDevice(new I2CDevice.Configuration(0, 50));
                }

                this.configuration = new I2CDevice.Configuration(address, clockRateKhz);
            }
        }

        /// <summary>
        /// Writes an array of bytes to the I2C device.
        /// </summary>
        /// <param name="writeBuffer">The array of bytes that will be sent to the I2C device.</param>
        /// <param name="timeout">The amount of time, in milliseconds, that the system will wait before resuming execution of the transaction.</param>
        /// <returns>The number of bytes of data transferred in the transaction.</returns>
        public int Write(byte[] writeBuffer, int timeout)
        {
            return Execute(new I2CDevice.I2CWriteTransaction[] { I2CDevice.CreateWriteTransaction(writeBuffer) }, timeout);
        }

        /// <summary>
        /// Reads an array of bytes from the device into a specified read buffer.
        /// </summary>
        /// <param name="readBuffer">The array of bytes that will contain the data read from the I2C device.</param>
        /// <param name="timeout">The amount of time, in milliseconds, that the system will wait before resuming execution of the transaction.</param>
        /// <returns>The number of bytes of data transferred in the transaction.</returns>
        public int Read(byte[] readBuffer, int timeout)
        {
            return Execute(new I2CDevice.I2CTransaction[] { I2CDevice.CreateReadTransaction(readBuffer) }, timeout);
        }

        /// <summary>
        ///  Writes an array of bytes to the I2C device, and reads an array of bytes from the device into a specified read buffer.
        /// </summary>
        /// <param name="writeBuffer">The array of bytes that will be sent to the I2C device.</param>
        /// <param name="readBuffer">The array of bytes that will contain the data read from the I2C device.</param>
        /// <param name="timeout">The amount of time, in milliseconds, that the system will wait before resuming execution of the transaction.</param>
        /// <returns>The number of bytes of data transferred in the transaction.</returns>
        public int WriteRead(byte[] writeBuffer, byte[] readBuffer, int timeout)
        {
            return Execute(new I2CDevice.I2CTransaction[] { I2CDevice.CreateWriteTransaction(writeBuffer), I2CDevice.CreateReadTransaction(readBuffer) }, timeout);
        }

        /// <summary>
        /// Performs a series of I2C transactions. 
        /// </summary>
        /// <remarks>
        /// This is a more advanced API for when <see cref="Write"/>, <see cref="Read"/> and <see cref="WriteRead"/> do not suffice. You may wish to use <see cref="I2CDevice.CreateWriteTransaction"/> and <see cref="I2CDevice.CreateReadTransaction"/> to create the transactions.
        /// </remarks>
        /// <param name="transactions">The list of transactions to perform.</param>
        /// <param name="timeout">The amount of time, in milliseconds, that the system will wait before resuming execution of the transaction.</param>
        /// <returns></returns>
        public int Execute(I2CDevice.I2CTransaction[] transactions, int timeout)
        {
            lock (I2CLock)
            {
                device.Config = this.configuration;
                return device.Execute(transactions, timeout);
            }
        }

    }

}
